package cn.sdormitory.component.bindValue;

import cn.hutool.core.util.ReflectUtil;
import cn.sdormitory.common.api.CommonPage;
import cn.sdormitory.common.api.CommonResult;
import cn.sdormitory.common.dto.ResultMsg;
import cn.sdormitory.common.utils.ContainerUtil;
import cn.sdormitory.common.utils.SpringUtils;
import cn.sdormitory.common.utils.StringUtils;
import cn.sdormitory.component.bindValue.annotation.AnnotationValue;
import cn.sdormitory.component.bindValue.annotation.BindStoreValue;
import cn.sdormitory.component.bindValue.bean.VariableEntry;
import cn.sdormitory.component.bindValue.bean.info.AnnotationInfo;
import cn.sdormitory.component.bindValue.bean.info.BindInfo;
import cn.sdormitory.component.bindValue.bean.info.BindStoreInfo;
import cn.sdormitory.component.bindValue.bean.info.StoreInfo;
import cn.sdormitory.component.bindValue.query.AnnotationQuery;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Table;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class AbstractBindValueHelper implements BindValueHelper{
    protected final Logger log = LoggerFactory.getLogger(getClass());

    protected final Map<Class<?>,List<BindInfo>> bindInfos = new ConcurrentHashMap<>();
    protected final Map<Class<?>,List<StoreInfo>> storeInfos = new ConcurrentHashMap<>();
    protected final Map<Class<?>,List<BindStoreInfo>> bindStoreInfos = new ConcurrentHashMap<>();

    protected @NotNull AnnotationContext<StoreInfo,Table<String,String,Object>> generateAnnotationContextByStoreInfo(
            StoreInfo info, List<?> dataList){
        return generateAnnotationContext(info, dataList);
    }

    protected @NotNull AnnotationContext<BindInfo,Map<String,String>> generateAnnotationContextByBindInfo(BindInfo info,
                                                                                                 List<?> dataList){
        return generateAnnotationContext(info, dataList);
    }


    private @NotNull <T extends AnnotationInfo<? extends Enum<?>>,R> AnnotationContext<T,R> generateAnnotationContext(
            T info, List<?> dataList){
        List<String> variables;
        Table<String,String,String> params;
        if(info.hasParamFields()){
            List<VariableEntry> entries = info.generateVariableEntry(dataList);
            variables = ContainerUtil.mapList(entries, VariableEntry::getVariable);
            params = info.generateParams(dataList,entries);
        }else {
            variables = info.generateVariables(dataList);
            params = info.getEmptyParams();
        }
        AnnotationContext<T,R> context = new AnnotationContext<>(info, variables, params);
        if(context.isEmptyVariables()){
            return context;
        }
        //根据xxxInfo的target类型获取对应的解析对象 使用解析对象根据变量值列表获取mapping信息
        @SuppressWarnings("unchecked")
        AnnotationQuery<T,R> annotationQuery = SpringUtils.getBean(StringUtils.toCamelCase(info.getTarget().name())+info.getTargetSuffixName(), AnnotationQuery.class);
        Assert.notNull(annotationQuery,()->"不支持的@"+info.getAnnotationClass().getSimpleName()+".target类型");
        context.setAnnotationQuery(annotationQuery);
        return context;
    }

    protected List<StoreInfo> getStoreInfos(Class<?> dataClas) {
//        if(storeInfos.containsKey(dataClas)){
//            return storeInfos.get(dataClas);
//        }
        Map<String, Field> fieldMap = getFieldMap(dataClas);
        Collection<Field> fields = fieldMap.values();
        List<StoreInfo> storeValues = fields.stream()
                .map(field -> generateAnnotationInfo(dataClas, field, fieldMap,StoreInfo.class))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        this.storeInfos.put(dataClas,storeValues);
        return storeValues;
    }

    /**
     * 获取绑定信息
     * @param dataClass 数据类型
     * @return 绑定信息
     */
    protected List<BindInfo> getBindInfos(Class<?> dataClass) {
        if(bindInfos.containsKey(dataClass)){
            return bindInfos.get(dataClass);
        }
        Map<String, Field> fieldMap = getFieldMap(dataClass);
        Collection<Field> fields = fieldMap.values();
        List<BindInfo> bindValues = fields.stream()
                .map(field -> generateAnnotationInfo(dataClass, field, fieldMap, BindInfo.class))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        this.bindInfos.put(dataClass,bindValues);
        return bindValues;
    }

    protected List<BindStoreInfo> getBindStoreInfos(Class<?> dataClass) {
        if(bindStoreInfos.containsKey(dataClass)){
            return bindStoreInfos.get(dataClass);
        }
        Map<String, Field> fieldMap = getFieldMap(dataClass);
        Collection<Field> fields = fieldMap.values();
        List<BindStoreInfo> bindValues = fields.stream()
                .map(field -> generateBindStoreInfo(dataClass, field, fieldMap))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        this.bindStoreInfos.put(dataClass,bindValues);
        return bindValues;
    }

    private @NotNull Map<String, Field> getFieldMap(@NotNull Class<?> dataClass) {
        List<Field> fields = getFields(dataClass);
        fields.forEach(ReflectionUtils::makeAccessible);
        return ContainerUtil.toMap(fields, Field::getName, Function.identity());
    }

    public static List<Field> getFields(Class<?> beanClass) throws SecurityException {
        List<Field> allFields = null;
        Class<?> searchType = beanClass;
        final Set<String> ignoreFieldNames = new HashSet<>();
        while (searchType != null) {
            Field[] declaredFields = searchType.getDeclaredFields();
            if(ContainerUtil.isEmpty(declaredFields)){
                searchType = searchType.getSuperclass();
                continue;
            }
            if (null == allFields) {
                allFields = Stream.of(declaredFields).collect(Collectors.toList());
                ignoreFieldNames.addAll(ContainerUtil.mapList(allFields, Field::getName));
            } else {
                List<Field> fields = ContainerUtil.filterList(declaredFields, field -> ignoreFieldNames.add(field.getName()));
                allFields.addAll(fields);
            }
            searchType = searchType.getSuperclass();
        }

        return allFields == null ? new LinkedList<>() : allFields;
    }

    private @Nullable BindStoreInfo generateBindStoreInfo(Class<?> dataClass, Field field, Map<String, Field> fieldMap) {
        BindStoreValue bindStoreValue = field.getAnnotation(BindStoreValue.class);
        if(bindStoreValue == null){
            return null;
        }

        Field variableField = getVariableFieldThrow(dataClass,fieldMap,
                bindStoreValue.variableField(),BindStoreValue.class);
        Field prefixField = StringUtils.isBlank(bindStoreValue.prefixField())
                ? null
                : getVariableFieldThrow(dataClass,fieldMap, bindStoreValue.prefixField(),BindStoreValue.class);

        return BindStoreInfo.generate(bindStoreValue, prefixField, variableField, field);
    }

    @SuppressWarnings("unused")
    private @Nullable <T> T generateAnnotationInfo(Class<?> dataClass, Field field, Map<String, Field> fieldMap, Class<T> returnClass) {
        AnnotationValue annotationValue = AnnotatedElementUtils.findMergedAnnotation(field, AnnotationValue.class);
        if(annotationValue == null){
            return null;
        }

        if(annotationValue.infoClass() != returnClass){
            // 获取到的annotation根据returnClass不匹配
            return null;
        }

        Annotation annotation = getAnnotationThrow(field, annotationValue.annotationClass());
        Object target = getAnnotationTargetThrow(annotation);
        String[] queryFields = getQueryFields(annotationValue, annotation);
        Field variableField = getVariableFieldThrow(dataClass,fieldMap,annotationValue,field);
        Field targetField = getTargetField(annotationValue,field);
        List<Field> paramFields = getParamFieldsThrow(dataClass,fieldMap,annotationValue);
        List<String> paramAliasList = ContainerUtil.mapList(annotationValue.paramFields(),
                paramField -> StringUtils.ifBlank(paramField.alias(),paramField.name()));

        @SuppressWarnings("rawtypes")
        AnnotationInfo info = ReflectUtil.newInstanceIfPossible(annotationValue.infoClass());
        //noinspection unchecked
        info.setTarget(target)
            .setTargetSuffixName(annotationValue.targetSuffixName())
            .setKey(annotationValue.key())
            .setDefaultValue(annotationValue.defaultValue())
            .setTargetField(targetField)
            .setVariableField(variableField)
            .setParamFields(paramFields)
            .setParamAliasList(paramAliasList)
            .setAnnotationClass(annotationValue.annotationClass());

        if(annotationValue.isStoreTarget()){
            ((StoreInfo)info).setQueryFields(queryFields);
        }

        //noinspection unchecked
        return (T) info;
    }

    private Annotation getAnnotationThrow(Field field, Class<? extends Annotation> annotationClass) {
        Annotation annotation = field.getAnnotation(annotationClass);
        Assert.notNull(annotation,()->"字段"+field.getName()+"上没有"+annotationClass.getSimpleName()+"注解");
        return annotation;
    }

    private String[] getQueryFields(AnnotationValue annotationValue, Annotation annotation) {
        if(annotationValue.isStoreTarget()){
             return (String[])getAnnotationValue(annotation, "queryFields");
        }
        return null;
    }

    private Object getAnnotationTargetThrow(Annotation annotation) {
        Object target = getAnnotationValue(annotation, "target");
        Assert.notNull(target,()->"注解"+annotation.annotationType().getSimpleName()+"的target属性不能为空");
        return target;
    }

    @SuppressWarnings("SameParameterValue")
    private static @Nullable Object getAnnotationValue(Annotation annotation, String attribute) {
        Method targetMethod = ReflectionUtils.findMethod(annotation.annotationType(), attribute);
        if(targetMethod == null){
            return null;
        }
        return ReflectionUtils.invokeMethod(targetMethod, annotation);
    }

    private List<Field> getParamFieldsThrow(Class<?> dataClass, Map<String, Field> fieldMap, AnnotationValue annotation) {
        return ContainerUtil.mapList(annotation.paramFields(), paramFieldAnnotation -> {
            Field paramField = fieldMap.get(paramFieldAnnotation.name());
            if (paramField == null) {
                String annotationName = "@"+annotation.annotationClass().getSimpleName();
                throw new RuntimeException(getNotFoundVariableFieldMsg(dataClass,annotationName));
            }
            return paramField;
        });
    }

    private Field getTargetField(AnnotationValue annotation, Field field) {
        return annotation.isStoreTarget() ? null : field;
    }

    private Field getVariableFieldThrow(@NotNull Class<?> dataClass,
                                        @NotNull Map<String, Field> fieldMap,
                                        @NotNull AnnotationValue annotation,
                                        @NotNull Field field) {
        if(annotation.isStoreTarget()){
            return field;
        }

        return getVariableFieldThrow(dataClass,fieldMap,annotation.variableField(),annotation.annotationClass());
    }

    private Field getVariableFieldThrow(Class<?> dataClass,
                                        Map<String, Field> fieldMap,
                                        String variableFieldName,
                                        Class<? extends Annotation> annotationClass) {
        Field variableField = fieldMap.get(variableFieldName);
        if(variableField == null){
            String annotationName = "@"+annotationClass.getSimpleName();
            throw new RuntimeException(getNotFoundVariableFieldMsg(dataClass, annotationName));
        }
        return variableField;
    }

    private static @NotNull String getNotFoundVariableFieldMsg(@NotNull Class<?> dataClass, String annotationName) {
        return "根据" + dataClass.getSimpleName() + "里的" +
                annotationName + "的variableField字段找不到目标field";
    }


    protected final boolean notSupport(Class<?> dataClass){
        return dataClass == null
                || Map.class.isAssignableFrom(dataClass)
                || Collection.class.isAssignableFrom(dataClass)
                || dataClass.isArray();
    }

    protected final Class<?> getDataClass(List<?> dataList) {
        if(CollectionUtils.isEmpty(dataList)){
            return null;
        }
        Object firstData = dataList.get(0);
        return firstData == null ? null : firstData.getClass();
    }

    protected List<?> getResDataList(Object res) {
        if(res instanceof CommonResult){
            // 如controller.getInfo
            CommonResult<?> result = (CommonResult<?>) res;
            if(result.isFailed() || result.getData() == null){
                return null;
            }
            res = result.getData();
        }else if(res instanceof ResultMsg){
            ResultMsg result = (ResultMsg) res;
            if(result.isFail() || result.getData() == null){
                return null;
            }
            res = result.getData();
        }
        Object returnListObj;
        if (res instanceof CommonPage) {
            CommonPage<?> page = (CommonPage<?>) res;
            returnListObj = page.getList();
        }else if(res instanceof IPage){
            IPage<?> page = (IPage<?>) res;
            returnListObj = page.getRecords();
        }else if(res instanceof Map){
            log.debug("bindValue不支持map对象");
            return null;
        }else {
            returnListObj = res;
        }
        return ContainerUtil.toList(returnListObj);
    }
}
